package com.demo.view;

import com.demo.model.Board;
import com.demo.model.Card;
import com.demo.model.Theme;
import sun.font.FontDesignMetrics;

import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * CardsView is a class to display cards
 */
public class CardsView extends JPanel {
    /** 单例模式 */
    private static final CardsView instance = new CardsView();

    /** 由主面板的宽决定画板视图的大小 */
    public static  int length = MainViewPanel.WIDTH;

    /** 内边距，画板和实际主体内容的距离 */
    public static  int padding = 12;

    /** 画板格子的行数 */
    public static  int rows = 4;

    /** 画板格子的列数 */
    public static  int columns = 4;

    /** 格子的间隔大小 */
    public static  int gap = padding;

    /** 由画板视图大小决定卡片的大小 */
    public static  int edge = (length - padding * 2 - gap * 3) / 4;

    /** 卡片位置的偏移量，负数表示往左上偏移，卡片原始位置将会留下阴影，表示卡片在上方空间的投影 */
    public static  int offset = -4;

    private final Map<String, Font> fontMap = new HashMap<>();

    private final List<Card> cards = Board.getInstance().getCards();

    private CardsView() {
    }

    /**
     * 绘制画板
     * <p>
     * 调用drawBackground()绘制背景，然后重复调用drawBlankGrid()绘制网格
     * <p>
     * @param g 普通画笔对象
     */
    private void drawBoard(Graphics g) {
        drawBackground(g, Theme.getBoardBackground());
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < columns; ++j) {
                drawBlankGrid(i, j, g, Theme.getBlankBackground());
            }
        }
    }

    /**
     * 绘制背景
     * <p>
     * 根据单一的颜色<code>color</code>填充背景
     * <p>
     * @param g 普通画笔对象
     * @param color 背景填充色
     */
    private void drawBackground(Graphics g, Color color) {
        g.setColor(color);
        g.fillRect(0, 0, length, length);
    }

    /**
     * 绘制单个网格格子
     * <p>
     * 在网格中的某个位置 (row, col) 绘制一个由 color 指定颜色填充的格子
     * <p>
     * @param row 画板中行标
     * @param col 画板中的列标
     * @param g 普通画笔对象
     * @param color 要填充的颜色
     */
    private void drawBlankGrid(int row, int col, Graphics g, Color color) {
        int x = padding + col * (edge + gap);
        int y = padding + row * (edge + gap);
        g.setColor(color);
        g.fillRect(x, y, edge, edge);
    }

    /**
     * 绘制卡片阴影
     *
     * 对这个卡片进行阴影绘制，原理是绘制一个形状一致的图形，然后设置不同的颜色，作为“阴影”
     *
     * @param card 卡片对象
     * @param g 普通画笔对象
     * @param color “阴影”颜色
     */
    private void drawCardShadow(Card card, Graphics g, Color color) {
        int x = card.getX() - offset;
        int y = card.getY() - offset;
        g.setColor(color);
        g.fillRect(x, y, card.getWidth(), card.getHeight());
    }

    /**
     * draw card !!
     *
     * draw from the bottom to the top according to the layer order,
     * followed by card shadow, card, number shadow and number.
     *
     *
     *   How to make number be CENTER?
     *   see: <a href="https://blog.csdn.net/zengrenyuan/article/details/80281738">...</a>
     *   to learn how to get width and height of a string displayed in some font
     *
     *
     * @param card a card object
     * @param g graphics context
     */
    protected void drawCard(Card card, Graphics g) {
        // 画一个正方形
        // 1. 先绘制阴影背景, 2. 再绘制本体
        Color shadow = Theme.getShadowColor();
        drawCardShadow(card, g, shadow);
        g.setColor(card.getColor());
        g.fillRect(card.getX(), card.getY(), card.getWidth(), card.getHeight());

        // 画一个数字
        // 1. 获取合适的font， 2. 计算文字显示的宽高， 3. 根据宽高计算居中时的坐标
        String number = String.valueOf(card.getNumber());
        int cardWidth = card.getWidth();
        int fontSize = 30;
        Font font = fontMap.computeIfAbsent(number, k -> getJustifyFont(number, edge,
                "Comic Sans MS", Font.BOLD, fontSize));

        int width = getContentWidth(number, font);
        int height = getContentHeight(font) - 8;

        int x = card.getX() + (cardWidth - width) / 2;
        int y = card.getY() + (cardWidth + height) / 2;

        g.setFont(font);
        // 画数字阴影，（设置暗一点的颜色，并在x,y加一点偏移量）
        g.setColor(card.getColor().darker());
        g.drawString(number, x + 2, y + 2);
        // 画数字
        g.setColor(Color.WHITE);
        g.drawString(number, x, y);

    }



    /**
     * 返回一个“合适”字体
     *
     * 根据给定字体大小、字体风格、字体名称和使用这些属性进行显示的文本，通过计算，返回一个在maxWidth之内的合适字体
     *
     * @param content 要显示的文本
     * @param maxWidth 限制的最大宽度
     * @param fontName 字体名称
     * @param fontStyle 字体风格
     * @param fontSize 字体大小
     * @return 返回一个符合要求的字体
     */
    public Font getJustifyFont(String content, int maxWidth, String fontName, int fontStyle, int fontSize) {
        Font justify;
        int width;
        do {
            justify = new Font(fontName, fontStyle, fontSize);
            width = getContentWidth(content, justify);
            --fontSize;
        } while (width >= maxWidth - maxWidth / 6);
        return justify;
    }

    /**
     * 返回字体的高度
     *
     * @param font 使用的字体
     * @return 字体的高度
     */
    private int getContentHeight(Font font) {
        return FontDesignMetrics.getMetrics(font).getMaxAscent();
    }

    /**
     * 返回使用某字体显示文本时，该文本的宽度
     *
     * @param content 要显示的文本
     * @param font 文本使用的字体
     * @return 文本显示的宽度
     */
    private int getContentWidth(String content, Font font) {
        int width = 0;
        FontMetrics metrics = FontDesignMetrics.getMetrics(font);
        for (int i = 0; i < content.length(); ++i) {
            width += metrics.charWidth(content.charAt(i));
        }
        return width;
    }

    /**
     * 父类组件进行图形绘制的入口
     */
    @Override
    protected void paintComponent(Graphics g) {
//        super.paintComponent(g);
        drawBoard(g);
        // 加上同步锁，避免遍历集合过程中元素删除导致的异常
        synchronized (cards) {
            for (Card card : cards) {
                drawCard(card, g);
            }
        }
    }

    /** 控制画板的大小 */
    @Override
    public Dimension getPreferredSize() {
        int size = MainViewPanel.WIDTH;
        return new Dimension(size, size);
    }

    /**
     * 返回x坐标
     * <p>
     *     根据给定的列，计算该坐标对应卡片的左上角的x坐标
     * </p>
     * <p>
     * @param column 列标
     * @return x值
     */
    public static int transColumnToX(int column) {
        return column * (edge + gap) + padding + offset;
    }

    /**
     * 返回y坐标，同上
     * <p>
     * @param row 给定行
     * @return y值
     */
    public static int transRowToY(int row) {
        return row * (edge + gap) + padding + offset;
    }


    /**
     * return current instance of CardsView
     *
     * @return JPanel instance
     */
    public static CardsView getInstance() {
        return instance;
    }
}
